use common::database::sdk_data::{DbComboTokenRow, DbSdkAccountRow, Password, Username};
use rand::distributions::{Alphanumeric, DistString};
use sqlx::{query_as, PgPool};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DbError {
    #[error("SQL error: {0}")]
    SqlxError(#[from] sqlx::Error),
    #[error("entry not found")]
    NotFound,
}

pub enum SelectSdkAccount {
    ByUsername(String),
    ByUid(i32),
}

pub async fn insert_sdk_account(
    pool: &PgPool,
    username: Username,
    password: Password,
) -> Result<DbSdkAccountRow, DbError> {
    let token = Alphanumeric.sample_string(&mut rand::thread_rng(), 64);

    Ok(query_as(
        "INSERT INTO t_sdk_account (token, username, password) VALUES ($1, $2, $3) RETURNING *",
    )
    .bind(token)
    .bind(username.as_str())
    .bind(password.as_hash_str())
    .fetch_one(pool)
    .await?)
}

pub async fn select_sdk_account(
    pool: &PgPool,
    select_mode: SelectSdkAccount,
) -> Result<DbSdkAccountRow, DbError> {
    match select_mode {
        SelectSdkAccount::ByUsername(username) => {
            query_as("SELECT * from t_sdk_account where username = ($1)")
                .bind(username)
                .fetch_optional(pool)
                .await?
                .ok_or(DbError::NotFound)
        }
        SelectSdkAccount::ByUid(uid) => query_as("SELECT * from t_sdk_account where uid = ($1)")
            .bind(uid)
            .fetch_optional(pool)
            .await?
            .ok_or(DbError::NotFound),
    }
}

pub async fn insert_combo_token(
    pool: &PgPool,
    account_uid: &str,
    device_id: &str,
) -> Result<DbComboTokenRow, DbError> {
    let token = Alphanumeric.sample_string(&mut rand::thread_rng(), 64);

    Ok(
        query_as("INSERT INTO t_combo_token VALUES ($1, $2, $3) RETURNING *")
            .bind(account_uid)
            .bind(token)
            .bind(device_id)
            .fetch_one(pool)
            .await?,
    )
}

pub async fn select_combo_token_by_account(
    pool: &PgPool,
    account_uid: &str,
) -> Result<DbComboTokenRow, DbError> {
    query_as("SELECT * from t_combo_token where account_uid = ($1)")
        .bind(account_uid)
        .fetch_optional(pool)
        .await?
        .ok_or(DbError::NotFound)
}
